package huriezy.quickcontacts.models.managers;

import huriezy.quickcontacts.R;
import huriezy.quickcontacts.models.LogDebug;
import huriezy.quickcontacts.models.interfaces.IContactManager;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import android.content.ContentProviderOperation;
import android.content.ContentProviderOperation.Builder;
import android.content.ContentResolver;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.util.Log;
import android.widget.Toast;

public class ContactManager implements IContactManager {

	private static final String	EMAIL_SYNTAX_REGEX	= "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?";

	private Context				parentContext;
	private Map<String, String>	currentContact;

	public ContactManager(Context parentContext) {
		this.parentContext = parentContext;
		currentContact = new HashMap<String, String>();
	}

	public String getContactData(String key) {
		return currentContact.get(key);
	}

	@Override
	public void putContactData(String key, String value) {
		currentContact.put(key, value);
	}

	@Override
	public boolean addCurrentContact() {
		return addContact(currentContact, -1);
	}

	public boolean addContact(Map<String, String> contactInfos, int userReplaced) {
		ContentResolver contentResolver = parentContext.getContentResolver();
		ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

		int userId = -1;
		if (userReplaced > 0) {
			userId = userReplaced;
		}
		else {
			userId = ops.size();
		}

		ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
				.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
				.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null).build());

		// Name
		String name = contactInfos.get(NAME_TAG);
		if (name != null && name.length() > 0) {
			Builder builder = newContentBuilder(userId);
			builder.withValue(ContactsContract.Data.MIMETYPE,
					ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
			builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
			ops.add(builder.build());
		}

		// Number
		String number = contactInfos.get(NUMBER_TAG);
		if (number != null && number.length() > 0) {
			Builder builder = newContentBuilder(userId);
			builder.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
			builder.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, number);
			builder.withValue(ContactsContract.CommonDataKinds.Phone.TYPE,
					ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
			ops.add(builder.build());
		}

		// Email
		String email = contactInfos.get(EMAIL_TAG);
		if (email != null && email.length() > 0) {
			Builder builder = newContentBuilder(userId);
			builder.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
			builder.withValue(ContactsContract.CommonDataKinds.Email.DATA, email);
			builder.withValue(ContactsContract.CommonDataKinds.Email.TYPE,
					ContactsContract.CommonDataKinds.Email.TYPE_MOBILE);
			ops.add(builder.build());
		}

		// Picture
		String pictureFilepath = contactInfos.get(PICTURE_TAG);
		if (pictureFilepath != null && pictureFilepath.length() > 0) {
			Bitmap bitmapPicture = BitmapFactory.decodeFile(pictureFilepath);
			if (bitmapPicture != null) {
				ByteArrayOutputStream stream = new ByteArrayOutputStream();
				bitmapPicture.compress(Bitmap.CompressFormat.PNG, 75, stream);

				Builder builder = newContentBuilder(userId);
				builder.withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1);
				builder.withValue(ContactsContract.Data.MIMETYPE,
						ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE);
				builder.withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, stream.toByteArray());
				ops.add(builder.build());
				try {
					stream.flush();
				}
				catch (IOException e) {
					Log.e(ContactManager.class.getSimpleName(), "Cannot flush byte stream", e);
					return false;
				}
			}
			else {
				Log.w(ContactManager.class.getSimpleName(), "Cannot add picture to contact : invalid filepath = "
						+ pictureFilepath);
				return false;
			}
		}

		// Asking the Contact provider to create a new contact
		try {
			contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);
		}
		catch (RemoteException e) {
			Toast toast = Toast
					.makeText(
							parentContext,
							"Cannot add the contact due to an internal error (RemoveException). Please contact the application developer.",
							Toast.LENGTH_LONG);
			toast.show();
			LogDebug.error("Error while trying to add new contact: ", getClass(), e);
		}
		catch (OperationApplicationException e) {
			Toast toast = Toast
					.makeText(
							parentContext,
							"Cannot add the contact due to an internal error (OperationApplicationException). Please contact the application developer.",
							Toast.LENGTH_LONG);
			toast.show();
			LogDebug.error("Error while trying to add new contact: ", getClass(), e);
		}

		Log.i(ContactManager.class.getSimpleName(), "Contact added : " + name);
		return true;
	}

	/**
	 * @return a prepared {@link Builder} for registering contact informations.
	 */
	private Builder newContentBuilder(int rawContactInsertIndex) {
		Builder builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
		builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
		return builder;
	}

	@Override
	public boolean verifyCorrectContact() {
		boolean result = true;

		String name = currentContact.get(IContactManager.NAME_TAG);
		String number = currentContact.get(IContactManager.NUMBER_TAG);
		String email = currentContact.get(IContactManager.EMAIL_TAG);

		String errorText = null;
		Toast toast = null;
		if (name == null || name.length() <= 0) {
			errorText = parentContext.getResources().getString(R.string.error_no_name_text);
			toast = Toast.makeText(parentContext, errorText, Toast.LENGTH_LONG);
		}
		else if (number == null || number.length() <= 0) {
			errorText = parentContext.getResources().getString(R.string.error_no_phone_text);
			toast = Toast.makeText(parentContext, errorText, Toast.LENGTH_LONG);
		}
		else if (email != null && email.length() > 0 && !email.matches(EMAIL_SYNTAX_REGEX)) {
			errorText = parentContext.getResources().getString(R.string.error_invalid_email_text);
			toast = Toast.makeText(parentContext, errorText, Toast.LENGTH_LONG);
		}

		if (toast != null) {
			toast.show();
			return false;
		}

		return result;
	}

	@Override
	public String getExistingContactIdByNumber(String number) {
		String id = null;

		Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
		ContentResolver contentResolver = parentContext.getContentResolver();
		Cursor contactLookup = contentResolver.query(uri, new String[] { BaseColumns._ID,
				ContactsContract.PhoneLookup.DISPLAY_NAME }, null, null, null);

		try {
			if (contactLookup != null && contactLookup.getCount() > 0) {
				contactLookup.moveToNext();
				id = contactLookup.getString(contactLookup.getColumnIndex(BaseColumns._ID));
			}
		} finally {
			if (contactLookup != null) {
				contactLookup.close();
			}
		}

		return id;
	}

	@Override
	public String getExistingContactIdByName(String name) {
		String id = null;

		Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(name));
		ContentResolver contentResolver = parentContext.getContentResolver();
		Cursor contactLookup = contentResolver.query(uri, new String[] { BaseColumns._ID,
				ContactsContract.PhoneLookup.DISPLAY_NAME }, null, null, null);

		try {
			if (contactLookup != null && contactLookup.getCount() > 0) {
				contactLookup.moveToNext();
				id = contactLookup.getString(contactLookup.getColumnIndex(BaseColumns._ID));
			}
		} finally {
			if (contactLookup != null) {
				contactLookup.close();
			}
		}

		return id;
	}

	/**
	 * 
	 */
	public String getContactById(String id) {
		StringBuilder contact = new StringBuilder();

		ContentResolver contentResolver = parentContext.getContentResolver();
		Cursor contactLookup = contentResolver.query(Contacts.CONTENT_URI, new String[] {
				BaseColumns._ID, PhoneLookup.DISPLAY_NAME, PhoneLookup.NUMBER }, BaseColumns._ID + "= ?", new String[] { id }, null);
		try {
			LogDebug.info("Row count : " + contactLookup.getCount(), getClass());
			if (contactLookup != null && contactLookup.getCount() > 0) {
				if (contactLookup.moveToNext()) {
					contact.append(contactLookup.getString(contactLookup.getColumnIndex(PhoneLookup.DISPLAY_NAME)));
					contact.append(IContactManager.CONTACT_SEPARATOR);
					contact.append(contactLookup.getString(contactLookup.getColumnIndex(PhoneLookup.NUMBER)));
				}
			}
		}
		catch (Exception e) {
			LogDebug.error("Exception while trying to read contact list : ", getClass(), e);
		} finally {
			if (contactLookup != null) {
				contactLookup.close();
			}
		}
		String result = contact.toString();
		LogDebug.info("Founded contact for id '" + id + "' : '" + result + "'", getClass());
		if (result.length() <= 0) {
			result = null;
		}
		return result;
	}

	@Override
	public void replaceContact(String userId) {
		if (userId != null) {
			try {
				int idInt = Integer.parseInt(userId);
				addContact(currentContact, idInt);
			}
			catch (NumberFormatException error) {
				LogDebug.error("Error while trying to parse contact id in integer : contact id is '" + userId + "'",
						getClass(), error);
			}
		}
		else {
			LogDebug.warn("No userId has been found for contact with id '" + userId + "'", getClass());
		}
	}
}
